<?php

namespace app\service;

/**
 * Redis延迟队列
 * Class RedisDelayQueue
 */
class RedisDelayQueue
{
    protected $prefix = 'delay_queue:';
    protected $redis = null;
    protected $key = '';

    private static $_instance = null;

    private function __construct($queue)
    {
        $this->key = $this->prefix . $queue;
        $this->redis = redis();
    }

    public static function getInstance($queue = '') {
        if(!self::$_instance) {
            self::$_instance = new RedisDelayQueue($queue);
        }
        return self::$_instance;
    }

    /**
     * 添加任务
     * @param $score
     * @param $value
     * @return int
     */
    public function addTask($score, $value)
    {
        //添加任务，key score1 member1 [score2 member2], 以时间作为score，对任务队列按时间从小到大排序
        $result = $this->redis->zAdd(
            $this->key,
            $score,
            json_encode($value, JSON_UNESCAPED_UNICODE)
        );
        if ($result) {
            debugLog("addTask: $score|" . json_encode($value), 'delayQueue');
        }
        return $result;
    }

    /**
     * 获取任务
     * @return array
     */
    public function getTask()
    {
        //获取任务，以0和当前时间为区间，返回一条记录
        return $this->redis->zRangeByScore($this->key, 0, time());
    }

    /**
     * 删除任务
     * @param $value
     * @return int
     */
    public function delTask($value)
    {
        return $this->redis->zRem($this->key, $value);
    }

    /**
     * 执行
     * @return bool
     */
    public function run()
    {
        //每次只取一条任务
        $task = $this->getTask();
        if (empty($task)) {
            return false;
        }

        $task = $task[0];
        //有并发的可能，这里通过zRem返回值判断谁抢到该任务
        if ($this->delTask($task)) {
            $data = json_decode($task, true);
            //处理任务
            debugLog("run: " . $task, 'delayQueue');
            return true;
        }
        return false;
    }

    /**
     * 处理延时队列
     * @param $key
     */
    public function deal($key)
    {
        while (true) {
            $res = $this->redis->zRangeByScore($key, 0, time(), ['limit' => [0, 1]]);
            echo '正常处理' . PHP_EOL;
            if (empty($res)) {
                sleep(1);
                continue;
            }
            $value = $res[0];
            $res   = $this->redis->zRem($key, $value);
            //在多线程处理时，只有删除成功的才有订单处理权
            if ($res) {
                //处理订单处理逻辑，更新订单状态，给用户发送提醒消息
                var_dump(sprintf("订单【%s】30分钟未支付，已自动取消", $value));
                //如果这里的任务处理失败，需要重新加入延时队列
            }
        }
    }
}